<?php
namespace Lvzmen\Helper;

/**
 * Class iArr
 * @package Lvzmen\Helper
 */
class iArr
{
    public $data;
    public $store; // 用于保存原始数据，便于复用

    public function __construct($data)
    {
        $this->data = $data;
        $this->store = $data;
    }

    /**
     * @param array $data
     */
    public static function setData(array $data)
    {
        return new self($data);
    }

    public function set($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * 获取所有数据
     * @return mixed
     */
    public function all()
    {
        return $this->data;
    }

    /**
     * 获取单条数据
     * @param $filter
     */
    public function one($filter)
    {
        if (iArrayHelper::isIndexed($this->data)) {
            $data = iArrayHelper::filterByValue($this->data, $filter, false);
            return $data[0] ?? [];
        }
        return $this->data;
    }

    /**
     * 获取单条数据
     * @param $id
     * @return array
     */
    public function findById($id): array
    {
        return $this->one(['id' => $id]);
    }


    /**
     * $strict 是指只筛选出在map中出现的字段返回
     * @param array $map
     * @param bool $strict
     * @return $this
     */
    public function map(array $map = [], bool $strict = false)
    {
        if ($strict) {
            $this->select(array_keys($map));
        }
        $this->data = iArrayHelper::setKeys($this->data, $map);
        return $this;
    }

    /**
     * 格式化
     * @param array $columns
     * @param int $scale
     * @return $this
     */
    public function toFloat($columns = [], $scale = 2)
    {
        foreach ($columns as $column) {
            if (iArrayHelper::isAssociative($this->data) && isset($this->data[$column])) {
                $this->data[$column] = is_numeric($this->data[$column]) ? iFormatHelper::float($this->data[$column], $scale) : 0;
            } elseif (is_array($this->data)) {
                foreach ($this->data as $key => $item) {
                    if (isset($item[$column])) {
                        $this->data[$key][$column] = is_numeric($item[$column]) ? iFormatHelper::float($item[$column], $scale) : 0;
                    }
                }
            }
        }
        return $this;
    }

    /**
     * 排序
     * @param $key
     * @param int $direction
     * @return $this
     */
    public function sortBy($key, $direction = SORT_ASC)
    {
        iArrayHelper::multisort($this->data, $key, $direction);
        return $this;
    }

    /**
     * 给指定的字段脱敏
     * @param $columns
     * @return $this
     */
    public function unSensible($columns)
    {
        $this->data = iFormatHelper::unSensible($this->data, $columns);
        return $this;
    }
    

    public function filter(array $filter, bool $strict = false, bool $like = false): self
    {
        foreach ($filter as $key => $value) {
            $this->data = iArrayHelper::filterByValue($this->data, [$key => $value], $strict, $like);
        }
        return $this;
    }

    /**
     * 占比的百分数形式和小数形式切换
     * @param array $columns
     * @param false $mode
     * @return $this
     */
    public function percent(array $columns = [], $mode = false)
    {
        if (iArrayHelper::isIndexed($this->data)) {
            $this->data = array_map(function ($item) use ($columns, $mode) {
                foreach ($columns as $column) {
                    $item[$column] = $mode ? $item[$column] * 100 : $item[$column] / 100;
                    $item[$column] = iFormatHelper::float($item[$column], $mode ? 2 : 4);
                }
                return $item;
            }, $this->data);
        } else {
            foreach ($columns as $column) {
                $this->data[$column] = $mode ? $this->data[$column] * 100 : $this->data[$column] / 100;
                $this->data[$column] = iFormatHelper::float($this->data[$column], $mode ? 2 : 4);
            }
        }
        return $this;
    }

    /**
     * 选取指定的字段
     * @param array $columns
     * @return $this
     */
    public function select(array $columns = [])
    {
        $this->data = array_map(function ($item) use ($columns) {
            $result = [];
            foreach ($columns as $column) {
                if (iArrayHelper::keyExists($column, $item)) {
                    $result[$column] = $item[$column];
                }
            }
            return $result;
        }, $this->data);
        return $this;
    }

    /**
     * 选取指定的字段
     *
     * example:
     * $arr = [
     *      ['col1' => 'val1', 'col2' => 'val2'],
     *      ['col1' => 'val1', 'col2' => 'val2']
     * ]
     *
     * self::getColumns($arr, ['col1'])
     *
     * result:
     * [
     *      ['col1' => 'val1'],
     *      ['col1' => 'val1'],
     * ]
     * @param array $columns
     */
    public function getColumns(array $columns = [])
    {
        $this->data = array_map(function ($item) use ($columns) {
            $result = [];
            foreach ($columns as $column) {
                if (iArrayHelper::keyExists($column, $item)) {
                    $result[$column] = $item[$column];
                }
            }
            return $result;
        }, $this->data);

        return $this;
    }


    public function getColumn($key, $unique = false): self
    {
        $data = iArrayHelper::getColumn($this->data, $key);
        $data = $unique ? array_unique($data) : $data;
        $this->data = array_values($data);
        return $this;
    }

    /**
     * 给数组新增字段
     * example:
     * $arr = [
     *      ['col1' => 'val1', 'col2' => 'val2'],
     *      ['col1' => 'val1', 'col2' => 'val2']
     * ]
     *
     * self::addColumns($arr, ['col3' => 'val3'])
     *
     * result:
     * [
     *      ['col1' => 'val1', 'col2' => 'val2', 'col3' => 'val3'],
     *      ['col1' => 'val1', 'col2' => 'val2', 'col3' => 'val3'],
     * ]
     * @param array $columns
     * @param array $where
     * @param bool $cover // 遇到相同key的数组，是否覆盖
     * @return self
     */
    public function addColumns(array $columns = [], array $where = [], bool $cover = false)
    {
        $this->data = array_map(function ($item) use ($columns, $where, $cover) {
            if (empty($where)) {
                foreach ($columns as $key => $value) {
                    if (iArrayHelper::keyExists($key, $item) && !$cover) {
                        continue;
                    }
                    $item[$key] = $value;
                }
            } else {
                $filter = iArrayHelper::filterByValue([$item], $where, false, true);
                if ($filter) {
                    foreach ($columns as $key => $value) {
                        if (iArrayHelper::keyExists($key, $item) && !$cover) {
                            continue;
                        }
                        $item[$key] = $value;
                    }
                }
            }
            return $item;
        }, $this->data);
        return $this;
    }

    /*
     * 显示返回的数量
     */
    public function limit($length): self
    {
        $this->data = array_slice($this->data, 0, $length);
        return $this;
    }


    public function trim(array $columns = ['*']): self
    {
        $this->data = iArrayHelper::trim($this->data, $columns, true);
        return $this;
    }


    /**
     * 行转列
     *
     * For example:
     *
     * ```php
     * $array = [
     *      [
     *          'day' => '2023-02-11',
     *          'fruit' => 'apple',
     *          'price' => 8.1,
     *      ],
     *      [
     *          'day' => '2023-02-11',
     *          'fruit' => 'orange',
     *          'price' => 5.1,
     *      ],
     *      [
     *          'day' => '2023-02-10',
     *          'fruit' => 'apple',
     *          'price' => 8.2,
     *      ],
     *      [
     *          'day' => '2023-02-10',
     *          'fruit' => 'orange',
     *          'price' => 5.2,
     *      ],
     * ];
     *
     * self::rowToColumn($array, ['day'], ['fruit' => 'price']);
     *
     * result：
     * [
     *     ['day' => '2023-02-11', 'apple' => 8.1, 'orange' => 5.1],
     *     ['day' => '2023-02-10', 'apple' => 8.2, 'orange' => 5.2],
     * ];
     * @param array $index
     * @param array $maps
     * @return $this
     */
    public function rowToColumn($index = [], $maps = []): self
    {
        $result = [];
        foreach ($this->data as $item) {
            $key = implode(',', array_map(function ($column) use ($item) {
                return $item[$column];
            }, $index));
            foreach ($index as $column) {
                $result[$key][$column] = $item[$column];
            }
            foreach ($maps as $from => $to) {
                $result[$key][$item[$from]] = $item[$to];
            }
        }
        $this->data = array_values($result);
        return $this;
    }

    /**
     * 获取最近的n条数据
     * @param int $limit
     * @param string $key
     * @param int $direction
     * @return $this
     */
    public function last($limit = 5, $key = '', $direction = SORT_ASC): self
    {
        $this->data = $this->sortBy($key, SORT_DESC)->limit($limit)->sortBy($key, $direction)->data;
        return $this;
    }

    /**
     * 恢复原始数据
     * @return $this
     */
    public function reset(): self
    {
        $this->data = $this->store;
        return $this;
    }

    /**
     * 计算数据条数
     * @return int
     */
    public function count(): int
    {
        return is_array($this->data) ? count($this->data) : 0;
    }

    /**
     * 计算某一列的和
     * @param string $column
     * @param int $scale
     * @return numeric
     */
    public function getSum($column = '', $scale = 2)
    {
        $result = array_reduce($this->data, function ($v, $item) use ($column) {
            return $v + (isset($item[$column]) && is_numeric($item[$column]) ? $item[$column] : 0);
        }, 0);
        return iFormatHelper::float($result, $scale);
    }


    /**
     * 计算两列累计和的比例
     *
     * For example:
     *
     * ```php
     * $array = [
     *      ['day' => '2023-02-11','pay' => 10,'used' => 8.1],
     *      ['day' => '2023-02-11','pay' => 10,'used' => 8.1],
     *      ['day' => '2023-02-11','pay' => 10,'used' => 8.1],
     *      ['day' => '2023-02-11','pay' => 10,'used' => 8.1],
     * ];
     *
     * self::getSumPercent('used', 'pay');
     *
     * result：
     * 81
     *
     * @param string $numerator // 分子列
     * @param string $denominator // 分母列
     * @param bool $percent
     * @param int $scale
     * @return numeric
     */
    public function getSumPercent($numerator = '', $denominator = '', $percent = true, $scale = 2)
    {
        $numerator = array_reduce($this->data, function ($v, $item) use ($numerator) {
            return $v + ($item[$numerator] ?? 0);
        }, 0);
        $denominator = array_reduce($this->data, function ($v, $item) use ($denominator) {
            return $v + ($item[$denominator] ?? 0);
        }, 0);
        $result = $denominator == 0 ? 0 : ($numerator / $denominator);
        $result = $percent ? $result * 100 : $result;
        return iFormatHelper::float($result, $scale);
    }


    /**
     * 统计占比
     * example:
     * $arr = [
     *      ['project' => 'name1', 'money' => 100],
     *      ['project' => 'name2', 'money' => 200]
     * ]
     *
     * self::rate('money')
     *
     * result:
     * [
     *      ['project' => 'name1', 'money' => 100, 'rate' => 20],
     *      ['project' => 'name2', 'money' => 400, 'rate' => 80]
     * ]
     *
     * @param $key // 统计维度
     * @param bool $percent // 是否以百分比的方式返回
     * @param int $scale // 保留小数位数
     * @return $this
     */
    public function rate($key, $percent = true, $scale = 2): self
    {
        $total = array_reduce($this->data, function ($v, $item) use ($key) {
            return $v + $item[$key];
        }, 0);
        $this->data = array_map(function ($item) use ($total, $key, $percent) {
            $item['rate'] = $total == 0 ? 0 : ($item[$key] / $total);
            $item['rate'] = $percent ? $item['rate'] * 100 : $item['rate'];
            return $item;
        }, $this->data);
        $this->toFloat(['rate'], $scale);
        return $this;
    }

    /**
     * 按照条件删除
     *
     * For example:
     *
     * ```php
     * $array = [
     *      ['day' => '2023-02-11','fruit' => 'apple','price' => 8.1,],
     *      ['day' => '2023-02-12','fruit' => 'apple','price' => 8.1,],
     *      ['day' => '2023-02-13','fruit' => 'apple','price' => 8.1,],
     * ];
     *
     * self::remove(['day' => '2023-02-11']);
     *
     * result：
     * [
     *      ['day' => '2023-02-12','fruit' => 'apple','price' => 8.1,],
     *      ['day' => '2023-02-13','fruit' => 'apple','price' => 8.1,],
     * ];
     *
     * @param array $where
     * @param false $like
     * @return $this
     */
    public function remove($where = [], $like = false): self
    {
        foreach ($this->data as $key => $item) {
            $check = false;
            foreach ($where as $k => $v) {
                if (!iArrayHelper::keyExists($k, $item)) {
                    continue;
                }
                if ($like) {
                    // 模糊匹配
                    $check = strpos($item[$k], $v) !== false;
                } else {
                    $check = $item[$k] == $v;
                }
            }
            if ($check) {
                unset($this->data[$key]);
            }
        }
        return $this;
    }

    /**
     * 对指定目录使用map
     * @param array $map
     * @param bool $strict
     * @return $this
     */
    public function mapInData(array $map = [], bool $strict = false): self
    {
        $this->data['data'] = (new self($this->data['data']))->map($map, $strict)->all();
        return $this;
    }


    /**
     * 行内计算某些字段之和
     * example:
     * $arr = [
     *      ['col1' => '1', 'col2' => '2', 'col3' => '3'],
     *      ['col1' => '1', 'col2' => '2', 'col3' => '3']
     * ]
     *
     * self::getSumOfColumns($arr, ['total1' => 'col1,col2', 'total2' => ['col2','col3']]])
     *
     * result:
     * [
     *      ['col1' => '1', 'col2' => '2', 'col3' => '3', 'total1' => 3, 'total2' => 5 ],
     *      ['col1' => '1', 'col2' => '2', 'col3' => '3', 'total1' => 3, 'total2' => 5 ],
     * ]
     * @param array $columns
     * @param array $where
     * @param bool $cover // 遇到相同key的数组，是否覆盖
     * @return self
     */
    public function getSumOfColumns(array $columns = []): self
    {
        $this->data = array_map(function ($item) use ($columns) {
            foreach ($columns as $key => $column) {
                if (is_string($columns)) {
                    $column = explode(',', $column);
                }
                $item[$key] = 0;
                foreach ($column as $c) {
                    $item[$key] += $item[$c] ?? 0;
                }
                $item[$key] = iFormatHelper::float($item[$key]);
            }
            return $item;
        }, $this->data);

        return $this;
    }

    /**
     * 追加字符
     * @param array $columns
     * @param string $str
     * @return $this
     */
    public function addString($columns = [], $str = '月'): self
    {
        $this->data = array_map(function ($item) use ($columns, $str) {
            foreach ($columns as $column) {
                $item[$column] = $item[$column] . $str;
            }
            return $item;
        }, $this->data);
        return $this;
    }

    /**
     * 格式化日期，实现递归处理
     * @param array $columns
     * @param string $format
     * @return $this
     */
    public function formatDay($columns = [], $format = 'm.d'): self
    {
        $this->data = $this->toolsFormatDay($this->data, $columns, $format);
        return $this;
    }

    private function toolsFormatDay($data, $columns = [], $format = 'm.d')
    {
        if (is_array($data)) {
            foreach ($data as $key => $item) {
                if (is_array($item)) {
                    $data[$key] = $this->toolsFormatDay($item, $columns, $format);
                } else {
                    if (in_array($key, $columns)) {
                        $data[$key] = date($format, strtotime($item));
                    }
                }
            }
        }
        return $data;
    }

    /**
     * @param array $by
     * @param array $map
     * @return $this
     */
    public function groupByForSum($by = [], $map = []): self
    {
        $this->data = array_map(function ($item) use ($map) {
            foreach ($map as $k => $v) {
                $item[$k] = is_numeric($item[$k]) ? $item[$k] : 0;
            }
            return $item;
        }, $this->data);
        $this->data = iArrayHelper::sum($this->data, $by, $map);
        return $this;
    }

    /**
     * 获取第一条数据
     * @return array
     */
    public function first(): array
    {
        return $this->data[0] ?? [];
    }

    /**
     * 字典设置
     * @param $name
     * @param array $dic
     * @param bool $recursive
     * @return $this
     *
     * @example
     * $data = [
     *  ['state' => 1],
     *  ['state' => 2],
     * ]
     *
     * $result = self::dic('state', ['1' => '草稿', '2' => '通过'])
     * $result = [['state' => '草稿', 'state' => '通过']]
     *
     *
     */
    public function dic($name, $dic = [], $recursive = true)
    {
        if ($recursive) {
            $this->data = $this->iDic($this->data, $name, $dic);
        } else {
            foreach ($this->data as $key => $item) {
                foreach ($item as $k => $v) {
                    if ($k == $name) {
                        $this->data[$key][$k] = $dic[$v] ?? '';
                    }
                }
            }
        }
        return $this;
    }

    private function iDic($data, $name, $dic = [])
    {
        if (is_array($data)) {
            foreach ($data as $key => $item) {
                if (is_array($item)) {
                    $data[$key] = $this->iDic($item, $name, $dic);
                }
                if ($key == $name) {
                    $data[$key] = $dic[$item];
                }
            }
        }
        return $data;
    }


    /**
     * 区间计数统计
     * @param $column
     * @param array $ranges
     *
     * @example
     * $arr = [
     *      ['name' => '苹果', 'price' => 9.9],
     *      ['name' => '香蕉', 'price' => 3.9],
     *      ['name' => '水蜜桃', 'price' => 15],
     * ]
     * self::rangeCount($arr, 'price', ['小于10元' => 10, '10元-20元', 20])
     * result = [
     *      ['小于10元' => 2],
     *      ['10元-20元' => 1]
     * ]
     *
     */
    public function rangeCount($column, $ranges = [])
    {
        $result = [];
        $lastValue = 0;
        foreach ($ranges as $key => $value) {
            foreach ($this->data as $item) {
                if ($item[$column] >= $lastValue && $item[$column] < $value) {
                    $result[$key] = isset($result[$key]) ? ($result[$key] + 1) : 1;
                }
            }
            $lastValue = $value;
        }
        $this->data = $result;
        return $this;
    }

    /**
     * 环比计算
     * @param array $columns
     * @param string $level // 用于出现$this->data['data']的情况
     * @return $this
     *
     * @example
     * $this->data = [
     *      ['month' => '1月', 'value' = 100],
     *      ['month' => '2月', 'value' = 200],
     *      ['month' => '3月', 'value' = 500],
     * ]
     * $result = $this->ratio(['value'])
     * $result:
     * [
     *      ['month' => '1月', 'value' = 100, 'value_radio' => 0],
     *      ['month' => '2月', 'value' = 200, 'value_radio' => 0.5],
     *      ['month' => '3月', 'value' = 500, 'value_radio' => 0.4],
     * ]
     */
    public function ratio($columns = [], $percent = true, $level = '')
    {
        $data = $level == '' ? $this->data : iArrayHelper::getValue($this->data, $level);
        foreach ($data as $key => $item) {
            foreach ($columns as $column) {
                if (isset($item[$column])) {
                    $data[$key][$column . '_ratio'] = (isset($data[$key - 1]) && $data[$key - 1][$column] != 0) ? (($data[$key][$column] - $data[$key - 1][$column]) / $data[$key - 1][$column]) : 0;
                    $data[$key][$column . '_ratio'] = $percent ? $data[$key][$column . '_ratio'] * 100 : $data[$key][$column . '_ratio'];
                    $data[$key][$column . '_ratio'] = iFormatHelper::float($data[$key][$column . '_ratio']);
                }
            }
        }
        if ($level == '') {
            $this->data = $data;
        } else {
            iArrayHelper::setValue($this->data, $level, $data);
        }
        return $this;
    }

    /**
     * 用于对某一个字段进行拆分
     * @param string $column
     * @param string $separator
     * @return $this
     *
     * @example
     * $this->data = [
     *      ['names' => '苹果,冬枣', 'location' => '山西'],
     *      ['names' => '香蕉', 'location' => '海南'],
     * ];
     * $result = $this->explode('names')
     * $result:
     * [
     *      ['names' => '苹果', 'location' => '山西'],
     *      ['names' => '冬枣', 'location' => '山西'],
     *      ['names' => '香蕉', 'location' => '海南'],
     * ];
     */
    public function explode($column = '', $separator = ',')
    {
        $result = [];
        foreach ($this->data as $item) {
            $items = explode($separator, $item[$column]);
            foreach ($items as $value) {
                $item[$column] = $value;
                $result[] = $item;
            }
        }
        $this->data = $result;
        return $this;
    }

    /**
     * 同php的array_map函数
     *
     * @param callable $callback
     * @param string $level
     * @return $this
     */
    public function arrayMap(callable $callback, $level = ''): self
    {
        $data = $level == '' ? $this->data : iArrayHelper::getValue($this->data, $level);
        $data = array_map($callback, $data);
        if ($level == '') {
            $this->data = $data;
        } else {
            iArrayHelper::setValue($this->data, $level, $data);
        }
        return $this;
    }

    /**
     * 同php的array_filter函数
     *
     * @param callable $callback
     * @param string $level
     * @return $this
     */
    public function arrayFilter(callable $callback, $level = ''): self
    {
        $data = $level == '' ? $this->data : iArrayHelper::getValue($this->data, $level);
        $data = array_values(array_filter($data, $callback));
        if ($level == '') {
            $this->data = $data;
        } else {
            iArrayHelper::setValue($this->data, $level, $data);
        }
        return $this;
    }

    /**
     * 列表求和
     *
     * @param string[] $name
     * @param array $map
     * @param int $location // 1：求和行放在第一行；2：求和行放在末行
     * @return $this
     */
    public function addRowOfSum($name = ['name', '总和'], $map = [], $location = 1)
    {
        $old = $this->data;
        $data = $this->addColumns(['total_sum' => $name[1]])->groupByForSum(['total_sum' => $name[0]], $map)->all();
        if ($location == 1) {
            $this->data = array_merge($data, $old);
        } else {
            $this->data = array_merge($old, $data);
        }
        return $this;
    }
}
